In this tutorial, we will create a full stack application using Django for the backend, FastAPI for the API, and Next.js for the frontend. This stack is fast, flexible, and has been used on many projects, including those with millions of users at AppSumo.
- Django: We use Django for its strong scaffolding abilities, primarily for its models, migrations, and admin capabilities.
- FastAPI: We choose FastAPI because of its speed, OpenAPI support, built-in docs, and Pydantic schemas and validation.
- Next.js: We utilize Next.js for its flexibility in providing server-side rendering (SSR), static site generation (SSG), and incremental static regeneration (ISR) on a per-page basis.
The source code for this tutorial can be found at https://github.com/damianhodgkiss/next-django-fastapi-fullstack-tutorial/. However, I recommend following the full tutorial to gain a comprehensive understanding of each step.
We choose to use Docker containers for hosting portability. While we may miss out on some instant auto-deployment features like those offered by Vercel, this configuration is designed for flexibility and major cloud providers such as AWS, Azure, and GCP. Additionally, self-hosting on a VPS with Portainer would be fairly straightforward with this stack too.
Prerequisites
Before we begin, ensure that you have the following installed:
- Python: If not already installed, download and install Python from the official website.
- django-admin: If not installed, run the following command to install it:
Note: Since this is a Docker application, we only need to install enough to set up Django and run create-next-app
on the host machine.
Steps
1. Create Project Directory
Create a new directory for the project and navigate into it:
mkdir next-django-fastapi-fullstack
cd next-django-fastapi-fullstack
2. Set Up Django Backend
2.1 Start Django Project
Create a backend directory and start a new Django project:
mkdir backend
django-admin startproject mysaas backend
2.2 Create Dockerfile
Create a Dockerfile
in the backend
directory with the following content:
FROM python:3.11.5-bullseye
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
RUN pip install --upgrade pip
COPY . /app/
RUN mkdir -p /app/staticfiles/
RUN pip install -r requirements.txt
EXPOSE 8000
CMD ["uvicorn", "mysaas.asgi:application", "--host", "0.0.0.0"]
Edit the requirements.txt
file in the backend
directory to include the necessary packages:
Django==5.0.3
uvicorn==0.25.0
fastapi==0.109.1
django-use-email-as-username==1.4.0
psycopg2==2.9.9
Edit the settings.py
file in the backend/mysaas
directory to configure the database and email authentication:
ALLOWED_HOSTS = ['localhost']
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.getenv("POSTGRES_DB", default="mysaas"),
"USER": os.getenv("POSTGRES_USER", default="mysaas"),
"PASSWORD": os.getenv("POSTGRES_PASSWORD", default="mysaas"),
"HOST": os.getenv("POSTGRES_HOST", default="postgres"),
"PORT": os.getenv("POSTGRES_PORT", default="5432"),
}
}
INSTALLED_APPS = [
"django_use_email_as_username.apps.DjangoUseEmailAsUsernameConfig",
...
]
2.5 Initialize PostgreSQL
Create a postgres
directory and an init.sql
file within it:
CREATE USER mysaas WITH PASSWORD 'mysaas';
CREATE DATABASE mysaas;
GRANT ALL PRIVILEGES ON DATABASE mysaas TO mysaas;
\connect mysaas;
GRANT CREATE ON SCHEMA public TO mysaas;
Add the following configuration for the PostgreSQL service in the docker-compose.yml
file:
postgres:
image: pgautoupgrade/pgautoupgrade:latest
restart: always
volumes:
- postgres-data:/var/lib/postgresql/data
- ./postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
POSTGRES_PASSWORD: postgres
volumes:
postgres-data:
2.6 Run Django
Start the Django application using Docker Compose:
docker compose up --build -d
Check if Django is running by opening http://localhost:8000 in your browser.
Before we configure the database and email authentication, let's set up a custom user model using django-use-email-as-username.
First, create a custom users app:
docker compose exec admin python manage.py create_custom_user_app users
Now, edit the backend/mysaas/settings.py
file to use the custom user model:
# Add 'users' to INSTALLED_APPS
INSTALLED_APPS = [
"users.apps.UsersConfig", # Add this line
# ... other installed apps ...
]
# Set the custom user model
AUTH_USER_MODEL = 'users.User'
Now we need to configure static files to ensure the Django admin interface works correctly.
Edit backend/mysaas/settings.py
to set the STATIC_ROOT
:
# Add this line at the end of the file
STATIC_ROOT = 'static'
Edit backend/mysaas/urls.py
to serve static files during development:
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Collect static files by running the following command:
docker compose exec admin python manage.py collectstatic
This command will gather all static files from your apps and place them in the directory specified by STATIC_ROOT
.
2.9 Run Migrations
Run the database migrations:
docker compose exec admin python manage.py migrate
Congratulations! Django is now configured.
3. Set Up FastAPI
3.1 Edit ASGI Configuration
Edit the asgi.py
file in the backend/mysaas
directory:
"""
ASGI config for mysaas project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
from fastapi import FastAPI
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysaas.settings")
application = get_asgi_application()
fastapp = FastAPI(
servers=[
{
"url": "/api/v1",
"description": "V1",
}
]
)
def init(app: FastAPI):
@app.get("/health")
def health_check():
return {'status': 'ok'}
init(fastapp)
3.2 Update Docker Compose Configuration
Add the FastAPI service to the docker-compose.yml
file:
api:
build:
context: backend
dockerfile: Dockerfile
image: backend:latest
ports:
- '8001:8000'
env_file:
- ./backend/.env
volumes:
- ./backend:/app
command: uvicorn mysaas.asgi:fastapp --host 0.0.0.0 --reload
depends_on:
- postgres
3.3 Run FastAPI
Start the FastAPI service using Docker Compose:
docker compose up --build -d
Check if FastAPI is running by opening http://localhost:8001/docs in your browser.
4. Set Up Next.js Frontend
4.1 Create Next.js App
Create a new Next.js app using the following command:
npx create-next-app@latest frontend --tailwind --typescript --eslint --app --src-dir --import-alias "@/*"
4.2 Create Dockerfile
Create a Dockerfile
in the frontend
directory:
FROM node:21-bookworm AS base
ARG DEBIAN_FRONTEND=noninteractive
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
# ENV NEXT_TELEMETRY_DISABLED 1
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
libc6-dev \
libvips-dev \
build-essential \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN chown node:node /app
## Install dependencies based on the preferred package manager, and build the app
FROM base AS builder
USER root
# COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
COPY --chown=node:node . .
USER node
RUN \
if [ -f yarn.lock ]; then yarn config set global-folder /app/.yarn && yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
RUN yarn build
## Copy the built app to a new image
FROM base AS runner
COPY --from=builder --chown=node:node /app/public ./public
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
COPY --from=builder --chown=node:node /app/node_modules/ ./node_modules/
USER node
ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/app/node_modules/.bin
EXPOSE 3000
CMD ["node", "server.js"]
Add the frontend service configuration to the docker-compose.yml
file:
frontend:
build:
context: frontend
image: frontend:latest
ports:
- '3000:3000'
volumes:
- ./frontend:/app
command: yarn dev
depends_on:
- api
Change the output mode to standalone
in the next.config.mjs
file:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
};
export default nextConfig;
4.4 Check Next.js
Verify that Next.js is running by opening http://localhost:3000 in your browser.
5.1 Create Nginx Configuration
Create an nginx
directory and an nginx.conf
file within it:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
client_max_body_size 100M; # allow large file uploads
keepalive_timeout 65;
#gzip on;
#include /etc/nginx/conf.d/*.conf;
upstream admin {
server admin:8000;
}
upstream api {
server api:8000;
}
upstream frontend {
server frontend:3000;
}
server {
listen 80;
location /admin {
proxy_pass http://admin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static {
proxy_pass http://admin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /media {
proxy_pass http://admin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /openapi.json {
proxy_pass http://api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /docs {
proxy_pass http://api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/api/v1(/?)(.*) {
proxy_pass http://api/$2$is_args$args;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://frontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
}
5.2 Add Nginx to Docker Compose
Add the Nginx service to the docker-compose.yml
file:
nginx:
image: nginx
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- frontend
- admin
- api
5.3 Start Nginx
Start Nginx using Docker Compose:
docker compose up --build -d
Check the following URLs to verify that everything is working correctly:
Conclusion
Congratulations! You have successfully created a full stack application using Django, FastAPI, and Next.js. This tutorial provided a step-by-step guide on setting up and configuring each component to work together seamlessly.
I will be providing more tutorials in the future that will use this starter as the basis for extending the application further.